package cn.yuanyuan.guide.parser.domain;

import java.util.ArrayList;
import java.util.List;

import cn.yuanyuan.guide.lexer.entity.Token;
import cn.yuanyuan.guide.parser.entity.ASTNode;
import cn.yuanyuan.guide.parser.entity.ASTRootNode;

/**
 * @author wuyitao
 * @date 2022/3/26 11:56 PM
 */
public class Parser {

    /**
     * But this time we're going to use recursion instead of a `while` loop. So we
     * define a `walk` function.
     *
     * @param index the `current` token index.
     * @param tokens token
     * @return a AST node
     */
    public ASTNode walk(int[] index, List<Token> tokens) {
        // Inside the walk function we start by grabbing the `current` token.
        Token token = tokens.get(index[0]);

        // We're going to split each type of token off into a different code path,
        // starting off with `number` tokens.
        //
        // We test to see if we have a `number` token.
        if ("number".equals(token.getType())) {

            // If we have one, we'll increment `current`.
            index[0]++;

            // And we'll return a new AST node called `NumberLiteral` and setting its
            // value to the value of our token.
            return new ASTNode("NumberLiteral", token.getValue());
        }

        // If we have a string we will do the same as number and create a
        // `StringLiteral` node.
        if ("string".equals(token.getType())) {
            index[0]++;
            return new ASTNode("StringLiteral", token.getValue());
        }

        // Next we're going to look for CallExpressions. We start this off when we
        // encounter an open parenthesis.
        if ("paren".equals(token.getType()) && "(".equals(token.getValue())) {
            // We'll increment `current` to skip the parenthesis since we don't care
            // about it in our AST.
            token = tokens.get(++index[0]);

            // We create a base node with the type `CallExpression`, and we're going
            // to set the name as the current token's value since the next token after
            // the open parenthesis is the name of the function.
            ASTNode node = new ASTNode("CallExpression", token.getValue(), new ArrayList<>());

            // We increment `current` *again* to skip the name token.
            token = tokens.get(++index[0]);

            // And now we want to loop through each token that will be the `params` of
            // our `CallExpression` until we encounter a closing parenthesis.
            //
            // Now this is where recursion comes in. Instead of trying to parse a
            // potentially infinitely nested set of nodes we're going to rely on
            // recursion to resolve things.
            //
            // To explain this, let's take our Lisp code. You can see that the
            // parameters of the `add` are a number and a nested `CallExpression` that
            // includes its own numbers.
            //
            //   (add 2 (subtract 4 2))
            //
            // You'll also notice that in our tokens array we have multiple closing
            // parenthesis.
            //
            //   [
            //     { type: 'paren',  value: '('        },
            //     { type: 'name',   value: 'add'      },
            //     { type: 'number', value: '2'        },
            //     { type: 'paren',  value: '('        },
            //     { type: 'name',   value: 'subtract' },
            //     { type: 'number', value: '4'        },
            //     { type: 'number', value: '2'        },
            //     { type: 'paren',  value: ')'        }, <<< Closing parenthesis
            //     { type: 'paren',  value: ')'        }, <<< Closing parenthesis
            //   ]
            //
            // We're going to rely on the nested `walk` function to increment our
            // `current` variable past any nested `CallExpression`.

            // So we create a `while` loop that will continue until it encounters a
            // token with a `type` of `'paren'` and a `value` of a closing
            // parenthesis.

            while (!"paren".equals(token.getType()) || !")".equals(token.getValue())) {
                // we'll call the `walk` function which will return a `node` and we'll
                // push it into our `node.params`.
                node.getParams().add(walk(index, tokens));
                token = tokens.get(index[0]);
            }
            // Finally we will increment `current` one last time to skip the closing
            // parenthesis.
            index[0]++;
            // And return the node.
            return node;
        }
        // Again, if we haven't recognized the token type by now we're going to
        // throw an error.
        throw new RuntimeException(token.getType());
    }
    /**
     * For our parser we're going to take our array of tokens and turn it into an
     * AST.
     *
     *   [{ type: 'paren', value: '(' }, ...]   =>   { type: 'Program', body: [...] }
     *
     * Okay, so we define a `parser` function that accepts our array of `tokens`.
     */
    public ASTRootNode parse(List<Token> tokens) {
        // Again we keep a `current` variable that we will use as a cursor.
        int[] current = new int[]{0};

        // Now, we're going to create our AST which will have a root which is a
        // `Program` node.
        ASTRootNode ast = new ASTRootNode("Program", new ArrayList<>());

        // And we're going to kickstart our `walk` function, pushing nodes to our
        // `ast.body` array.
        //
        // The reason we are doing this inside a loop is because our program can have
        // `CallExpression` after one another instead of being nested.
        //
        //   (add 2 2)
        //   (subtract 4 2)
        //
        while (current[0] < tokens.size()) {
            ast.getBody().add(walk(current, tokens));
        }

        // At the end of our parser we'll return the AST.
        return ast;
    }
}
